#include "guiterm.hpp"

RemoteJob::RemoteJob(Session *session)
{
	this->session = session;
	this->inputSender = NULL;
	this->command = "";
	this->isLogin = true;
	this->isNestedShell = false;
	
	this->started = false;
	this->isFinished = false;
	this->jobid = session->getHost()->newJob();
	
	this->view = new JobView(this, false, "");
	
	this->currentOutputModel = NULL;
	this->currentTextSegment = NULL;
	this->state = stateNormal;
}

RemoteJob::RemoteJob(Session *session, string cmd)
{
	this->session = session;
	this->inputSender = NULL;
	this->command = cmd;
	this->isLogin = false;
	this->isNestedShell = false;
	
	this->started = false;
	this->isFinished = false;
	this->jobid = session->getHost()->newJob();
	
	this->view = new JobView(this, true, cmd);
	
	this->currentOutputModel = NULL;
	this->currentTextSegment = NULL;
	this->state = stateNormal;
}

RemoteJob::~RemoteJob()
{
	if(inputSender)
		delete inputSender;
}

void RemoteJob::begin()
{
	pair<int,int> size = session->getTerminalSize(view->getHeaderHeight());
	
	RPCParams runCommandParams;
	runCommandParams.set("job", jobid);
	runCommandParams.set("width", size.first);
	runCommandParams.set("height", size.second);
	
	if(isLogin) {
		session->getHost()->call("init", &runCommandParams);
	} else {
		runCommandParams.set("cmd", command);
		session->getHost()->call("p", &runCommandParams);
	}
	
	session->getHost()->attachJob(this);
	started = true;
	
	session->addSegment(view);
	view->giveFocus();
}

Session *RemoteJob::getSession()
	{ return session; }
int RemoteJob::getId() const
	{ return jobid; }
JobView *RemoteJob::getView()
	{ return view; }
bool RemoteJob::getIsFinished()
	{ return isFinished; }

RPC_HANDLER(finished)
{
	RemoteJob *job = getJob(host, params);
	int result = params->getInt("result");
	string leftover = params->getString("leftover");
	
	job->finish(result, leftover);
}

void RemoteJob::finish(int result, string leftoverInput)
{
	if(result != 0) {
		char message[256];
		sprintf(message, "Returned status code %i", result);
		reportError(message);
	}
	
	if(inputSender) {
		delete inputSender;
		inputSender = NULL;
	}
	
	isFinished = true;
	
	session->jobFinished(this, result);
	
	// If focused, pass leftover input to the next prompt
	if(view && view->isFocused()) {
		session->addLeftoverInput(leftoverInput);
	}
	
	// Mark the view as finished
	view->jobFinished(result);
}

RPC_HANDLER(error)
{
	RemoteJob *job = getJob(host, params);
	string message = params->getString("message");
	
	if(job)
		job->reportError(message.c_str());
}

void RemoteJob::reportError(string message)
{
	MessageSegment *segment = new MessageSegment(session, message, MessageSegment::ErrorMessage);
	view->addSegment(segment);
}
void RemoteJob::reportMessage(string message)
{
	MessageSegment *segment = new MessageSegment(session, message, MessageSegment::InfoMessage);
	view->addSegment(segment);
}

void RemoteJob::beginOutput()
{
	currentOutputModel = NULL;
	currentTextSegment = NULL;
}

void RemoteJob::setRunState(string state)
{
	if(state == "background")
		session->jobBackgrounded(this);
	else if(state == "running")
		view->giveFocus();
}

void RemoteJob::programOutput(const char *data, int len)
{
	for(int ii=0; ii<len; ii++)
	{
		switch(state)
		{
			case stateNormal:
				if(data[ii]=='\x1b')
					state = stateEscSeen;
				break;
			case stateEscSeen:
				if(data[ii]==']')
					state = stateOSC;
				else
					state = stateNormal;
				break;
			case stateOSC:
				if(data[ii]=='\x1b') {
					state = stateOSCEscSeen;
				} else if(data[ii]=='\a' || data[ii]=='\n') {
					handleOSC(data, len, ii);
					state = stateNormal;
				} else {
					pendingOSC.appendByte(data[ii]);
				}
				break;
			case stateOSCEscSeen:
				if(data[ii]=='\\' || data[ii]=='\n') {
					handleOSC(data, len, ii);
					state = stateNormal;
				} else {
					state = stateOSC;
				}
				break;
		}
	}
	
	if(!currentOutputModel)
		addTextOutput();
	currentOutputModel->receiveData(data, len);
}

void RemoteJob::outputFinished()
{
	// TODO
}

void RemoteJob::sendData(const char *data, int len)
{
	if(isNestedShell)
		return;
	
	if(!inputSender)
		createInputSender();
	
	inputSender->send(len, data);
}

void RemoteJob::createInputSender()
{
	RPCParams programInputParams;
		programInputParams.set("job", jobid);
		inputSender = new StreamSender(session->getHost(), &programInputParams);
	session->getHost()->call("input", &programInputParams);
}

void RemoteJob::winch(int width, int height)
{
	if(started)
	{
		RPCParams windowResizeParams;
			windowResizeParams.set("job", jobid);
			windowResizeParams.set("width", width);
			windowResizeParams.set("height", height);
		session->getHost()->call("winch", &windowResizeParams);
	}
}

void RemoteJob::addFileView(FileView *fileView)
{
	view->addSegment(fileView);
	fileView->setFocusProxy(session->getPrompt());
}

void RemoteJob::finishSegment()
{
	RPCParams finishSegmentParams;
		finishSegmentParams.set("job", jobid);
		finishSegmentParams.set("sig", "done");
	session->getHost()->call("signal", &finishSegmentParams);
}

void RemoteJob::interrupt()
{
	RPCParams terminateParams;
		terminateParams.set("job", jobid);
		terminateParams.set("sig", "int");
	session->getHost()->call("signal", &terminateParams);
}

void RemoteJob::terminate()
{
	RPCParams terminateParams;
		terminateParams.set("job", jobid);
		terminateParams.set("sig", "term");
	session->getHost()->call("signal", &terminateParams);
}

void RemoteJob::kill()
{
	RPCParams terminateParams;
		terminateParams.set("job", jobid);
		terminateParams.set("sig", "kill");
	session->getHost()->call("signal", &terminateParams);
}

void RemoteJob::pause()
{
	RPCParams pauseParams;
		pauseParams.set("job", jobid);
		pauseParams.set("sig", "stop");
	session->getHost()->call("signal", &pauseParams);
}

void RemoteJob::addTextOutput()
{
	CharacterViewConfig *viewConfig = session->getTerminalSettings();
	pair<int,int> size = session->getTerminalSize(view->getHeaderHeight());
	
	currentOutputModel = new TerminalEmulator(this, size.first, size.second);
	currentTextSegment = new ScrollableTermView(this, currentOutputModel, viewConfig, true);
	currentOutputModel = currentTextSegment->getModel();
	
	view->addSegment(currentTextSegment);
	currentTextSegment->finalizeSize();
}

void RemoteJob::handleOSC(const char *&incomingData, int &incomingLen, int incomingPos)
{
	string osc = string(pendingOSC.getData(), pendingOSC.getLen());
	pendingOSC.clear();
	
	if(osc=="gbegin")
	{
		char *protocolStart = "\x1b]gbegin\n";
		sendData(protocolStart, strlen(protocolStart));
		
		ServerConnection *nestedHost = startNestedSession();
		
		int followingDataStart = incomingPos+1;
		while(followingDataStart < incomingLen && (incomingData[followingDataStart]=='\n' || incomingData[followingDataStart]=='\\'))
			followingDataStart++;
		if(followingDataStart < incomingLen)
			nestedHost->receivedData(incomingLen-followingDataStart, incomingData+followingDataStart);
		
		incomingLen = incomingPos+1;
	}
}

ServerConnection *RemoteJob::startNestedSession()
{
	if(!inputSender)
		createInputSender();
	
	NestedShellDescription *nestedDescription = new NestedShellDescription(this);
	view->addSegment(nestedDescription);
	session->jobBackgrounded(this);
	
	isNestedShell = true;
	ServerConnection *nestedHost = new ServerConnection(session->getHost(), jobid, inputSender);
	Session *nestedSession = new Session(nestedHost);
	session->getTab()->getWindow()->addTab(nestedSession->getTab());
	nestedSession->begin();
	
	return nestedHost;
}

